package com.chushouya.common.applet;

import cn.hutool.core.util.URLUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import com.chushouya.common.enums.RedisKeys;
import com.chushouya.common.enums.RedisLocks;
import com.chushouya.common.support.DevOption;
import com.chushouya.common.wx.dto.OfficialUserInfo;
import com.chushouya.common.wx.dto.WxMessagePushRequest;
import com.general.framework.core.exception.Ex;
import com.general.framework.core.lang.Jsons;
import com.general.framework.core.lang.Strings;
import com.general.framework.core.lang.Validate;
import com.general.framework.core.utils.WxApiRequest;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 微信客户端
 */
@Slf4j
public class WxClient {
    private static final int SUCCESS = 0;
    private final String appid;
    private final String secret;

    public WxClient(String appid, String secret) {
        this.appid = appid;
        this.secret = secret;
    }

    /**
     * 微信授权登录
     * <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/code2Session.html">文档</a>
     */
    public String jscode2session(String code) {

        Validate.isBlank(code, "code不能为空");

        Map<String, Object> map = new HashMap<>();
        map.put("appid", appid);
        map.put("secret", secret);
        map.put("js_code", code);
        map.put("grant_type", "authorization_code");

        String url = "https://api.weixin.qq.com/sns/jscode2session?" + URLUtil.buildQuery(map, StandardCharsets.UTF_8);
        String resp = HttpUtil.get(url);
        log.debug("微信授权登录响应:{}", resp);
        JSONObject json = Jsons.parseObject(resp);
        if (json.getIntValue("errcode") != SUCCESS) {
            log.error("微信授权登录异常: {}", resp);
            throw Ex.business("微信授权登录异常:{}", json.getString("errmsg"));
        }
        return json.getString("openid");
    }

    /**
     * 公众号授权登录获取用户信息
     * <a href="https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html">文档</a>
     */
    public OfficialUserInfo jsCode2OfficialUserInfo(String code) {
        Validate.isBlank(code, "code不能为空");
        String url = Strings.format("https://api.weixin.qq.com/sns/oauth2/access_token?appid={}&secret={}&code={}&grant_type=authorization_code", appid, secret, code);
        String resp = HttpUtil.get(url);
        log.debug("公众号授权登录获取用户信息响应:{}", resp);
        JSONObject json = Jsons.parseObject(resp);
        if (json.getIntValue("errcode") != SUCCESS) {
            log.error("微信授权登录异常: {}", resp);
            throw Ex.business(json.getString("errmsg"));
        }
        OfficialUserInfo userInfo = new OfficialUserInfo();
        userInfo.setUnionId(json.getString("unionid"));
        userInfo.setOpenId(json.getString("openid"));
        return userInfo;
    }

    /**
     * code换取手机号码
     * <a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html">文档</a>
     *
     * @return 手机号码
     */
    public String getPhoneNumber(String code) {

        Validate.isBlank(code, "获取手机号授权code不能为空");

        Map<String, Object> map = new HashMap<>();
        map.put("code", code);
        String resp = HttpUtil.post("https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + getAccessToken(), Jsons.toJsonString(map));
        log.debug("获取手机号响应:{}", resp);
        JSONObject json = Jsons.parseObject(resp);
        if (json.getIntValue("errcode") != SUCCESS) {
            log.error("微信code换取手机号码结果异常, code:{}, 响应:{}", code, resp);
            if (json.getIntValue("errcode") == 40001) {
                deleteAccessToken();
            }
            throw Ex.business(json.getString("errmsg"));
        }
        return json.getJSONObject("phone_info").getString("purePhoneNumber");
    }

    /**
     * token 过期删除缓存
     */
    public void deleteAccessToken() {
        RedisHolder.REDIS_CACHE.deleteObject(Strings.format(RedisKeys.ACCESS_TOKEN, appid));
    }

    /**
     * 先从缓存读取，不存在请求微信获取最新的token, 获取成功后缓存起来
     * <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html">文档</a>
     *
     * @return accessToken
     */
    private String getAccessToken() {

        final String accessTokenRedisKey = Strings.format(RedisKeys.ACCESS_TOKEN, appid);
        if (RedisHolder.REDIS_CACHE.exist(accessTokenRedisKey)) {
            return RedisHolder.REDIS_CACHE.getCacheObject(accessTokenRedisKey);
        }

        //不存在获取最新
        RLock lock = RedisHolder.REDISSON_CLIENT.getLock(Strings.format(RedisLocks.ACCESS_TOKEN_LOCK, appid));
        try {
            //获取锁超时
            if (!lock.tryLock(5, TimeUnit.SECONDS)) {
                throw Ex.business("服务繁忙, 请稍后重试");
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        try {
            if (RedisHolder.REDIS_CACHE.exist(accessTokenRedisKey)) {
                return RedisHolder.REDIS_CACHE.getCacheObject(accessTokenRedisKey);
            }

            JSONObject json;
            if (DevOption.isDevModel()) {
                // 开发环境使用服务端Token
                json = requestDevAccessToken();
            } else {
                // 生产环境使用稳定版Token
                json = requestStableAccessToken();
            }
            if (json == null) {
                throw Ex.business("获取accessToken异常");
            }
            String accessToken = json.getString("access_token");
            if (Strings.isBlank(accessToken)) {
                throw Ex.business(json.containsKey("errcode") ? json.getString("errcode") : "获取accessToken异常");
            }
            RedisHolder.REDIS_CACHE.setCacheObject(accessTokenRedisKey, accessToken, json.getLongValue("expires_in"), TimeUnit.SECONDS);
            return accessToken;
        } finally {
            lock.unlock();
        }
    }

    /**
     * 从微信获取最新的accessToken
     */
    public JSONObject requestAccessToken() {

        Map<String, Object> map = new HashMap<>();
        map.put("grant_type", "client_credential");
        map.put("appid", appid);
        map.put("secret", secret);

        String url = "https://api.weixin.qq.com/cgi-bin/token?" + URLUtil.buildQuery(map, StandardCharsets.UTF_8);
        String resp = HttpUtil.get(url);
        log.debug("从微信获取最新的accessToken响应:{}", resp);
        JSONObject json = Jsons.parseObject(resp);
        if (json.getInteger("errcode") != null) {
            log.info("微信获取accessToken异常: {}", resp);
            throw Ex.business(json.getString("errmsg"));
        }
        return json;
    }

    /**
     * 获取稳定版接口调用凭据
     * <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getStableAccessToken.html">文档</a>
     */
    public JSONObject requestStableAccessToken() {
        Map<String, Object> map = new HashMap<>();
        map.put("grant_type", "client_credential");
        map.put("appid", appid);
        map.put("secret", secret);
        log.info("获取稳定版接口调用凭据参数:{}", map);
        String url = "https://api.weixin.qq.com/cgi-bin/stable_token";
        String resp = HttpUtil.post(url, Jsons.toJsonString(map));
        log.debug("获取稳定版接口调用凭据响应:{}", resp);
        JSONObject json = Jsons.parseObject(resp);
        if (json.getInteger("errcode") != null) {
            log.info("获取稳定版接口调用凭据异常: {}", resp);
            throw Ex.business(json.getString("errmsg"));
        }
        return json;
    }

    /**
     * 开发环境获取服务端AccessToken
     * 本地开发时由于IP限制，通过服务端API获取Token
     */
    private JSONObject requestDevAccessToken() {
        try {
            String url = "https://api.chushouya.com/open/cgi-bin/token";
            Map<String, Object> params = new HashMap<>();
            params.put("appid", appid);
            params.put("secret", secret);
            log.debug("开发环境获取服务端AccessToken参数:{}", params);
            String resp = HttpUtil.post(url, Jsons.toJsonString(params));
            log.debug("开发环境获取服务端AccessToken响应:{}", resp);
            
            JSONObject responseJson = Jsons.parseObject(resp);
            
            // 检查服务端响应是否成功
            if (responseJson.getInteger("code") != 200) {
                log.info("开发环境获取服务端AccessToken失败: {}", resp);
                throw Ex.business("服务端响应异常: " + responseJson.getString("msg"));
            }
            
            // 提取data中的微信Token数据
            JSONObject tokenData = responseJson.getJSONObject("data");
            if (tokenData == null) {
                log.info("开发环境获取服务端AccessToken数据为空: {}", resp);
                throw Ex.business("服务端返回数据为空");
            }
            
            // 检查微信Token数据是否有效
            if (tokenData.getString("access_token") == null) {
                log.info("开发环境获取服务端AccessToken无效: {}", resp);
                throw Ex.business("服务端返回Token无效");
            }
            
            return tokenData;
        } catch (Exception e) {
            log.error("开发环境获取服务端AccessToken失败: {}", e.getMessage(), e);
            // 如果服务端获取失败，降级使用本地获取
            log.warn("服务端Token获取失败，降级使用本地获取");
            return requestStableAccessToken();
        }
    }

    /**
     * 发送小程序订阅消息
     *
     * @param templateId       模板id
     * @param page             点击模板卡片后的跳转页面，仅限本小程序内的页面。支持带参数,（示例index?foo=bar）
     * @param touser           接收者（用户）的 openid
     * @param data             模板内容
     * @param miniprogramState 跳转小程序类型：developer为开发版；trial为体验版；formal为正式版；默认为正式版
     */
    public void sendMessage(String templateId, String page, String touser, LinkedHashMap<String, String> data, String miniprogramState) {
        JSONObject body = new JSONObject();
        body.put("template_id", templateId);
        body.put("touser", touser);
        if (data != null) {
            body.put("data", MessageUtil.buildMessageData(data));
        }
        if (Strings.isNotBlank(page)) {
            body.put("page", page);
        }
        if (Strings.isNotBlank(miniprogramState)) {
            body.put("miniprogram_state", miniprogramState);
        }
        String resp = HttpUtil.post("https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=" + getAccessToken(), body.toString());
        log.debug("发送小程序订阅消息响应:{}", resp);
        JSONObject json = Jsons.parseObject(resp);
        if (json.getIntValue("errcode") != SUCCESS) {
            if (json.getIntValue("errcode") == 40001) {
                deleteAccessToken();
            }
            throw Ex.business(json.getString("errmsg"));
        }
    }

    /**
     * 发送小程序订阅消息
     *
     * @param templateId 模板id
     * @param touser     接收者（用户）的 openid
     * @param data       模板内容
     */
    public void sendMessage(String templateId, String touser, LinkedHashMap<String, String> data) {
        sendMessage(templateId, "", touser, data, "");
    }

    /**
     * 通过openid换取unionid,
     * 前提: 当前应用必须在微信开放平台绑定了同一个账号主体
     *
     * @param openId 用户openid
     */
    public String getUnionIdByOpenId(String openId) {
        String resp = HttpUtil.get("https://api.weixin.qq.com/cgi-bin/user/info?access_token=" + getAccessToken() + "&openid=" + openId + "&lang=zh_CN");
        log.debug("通过openid换取unionid响应:{}", resp);
        JSONObject json = Jsons.parseObject(resp);
        Integer errcode = json.getInteger("errcode");
        if (errcode != null && errcode != SUCCESS) {
            log.error("通过openid换取unionid:{}", json.getString("errmsg"));
            throw Ex.business("获取unionId失败");
        }
        String unionId = json.getString("unionid");
        return unionId == null ? "" : unionId;
    }

    /**
     * 发送公众号模板消息
     * <a href="https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Template_Message_Interface.html#发送模板消息">参考文档</a>
     *
     * @param message 消息内容
     */
    public void sendOfficialTemplateMessage(WxMessagePushRequest message) {
        // 请求地址
        Map<String, Object> requestParam = new HashMap<>();
        requestParam.put("touser", message.getOpenId());
        requestParam.put("template_id", message.getTemplateId());
        if (Strings.isNotEmpty(message.getLinkPage())) {
            requestParam.put("url", message.getLinkPage());
        }
        // 小程序数据
        if (Strings.isNotBlank(message.getLiteLinkPage())) {
            Map<String, Object> miniParam = new HashMap<>();
            miniParam.put("appid", message.getLiteAppId());
            miniParam.put("pagepath", message.getLiteLinkPage());
            requestParam.put("miniprogram", miniParam);
        }
        requestParam.put("data", message.getData());
        final String request = Jsons.toJsonString(requestParam);
        log.info("发送公众号模板消息请求:{}", request);
        String resp = HttpUtil.post("https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + getAccessToken(), request);
        log.info("发送公众号模板消息响应:{}", resp);
        JSONObject json = Jsons.parseObject(resp);
        Integer errcode = json.getInteger("errcode");
        if (errcode != null && errcode != SUCCESS) {
            if (errcode == 40001) {
                deleteAccessToken();
            }
            throw Ex.business(json.getString("errmsg"));
        }
    }

    /**
     * 获取小程序二维码base64
     *
     * @param page 页面路径
     * @param data 二维码参数
     */

    public String getQRCodeBase64(String page, String data){
        final String accessToken = getAccessToken();
        final Map<String, Object> requestParam = new HashMap<>(2);
        requestParam.put("page", page);
        requestParam.put("scene", data);
        requestParam.put("check_path", false);
        requestParam.put("env_version", DevOption.isDevModel() ? "develop" : "release");
        return WxApiRequest.bytes2Base64(WxApiRequest.getUnlimitedQRCode(accessToken, requestParam));
    }

    public byte[] getQRCodeImageBytes(String page, String data){
        final Map<String, Object> requestParam = new HashMap<>(2);
        requestParam.put("page", page);
        requestParam.put("scene", data);
        requestParam.put("check_path", false);
        requestParam.put("env_version", DevOption.isDevModel() ? "develop" : "release");
        return WxApiRequest.getUnlimitedQRCode(getAccessToken(), requestParam);
    }

    /**
     * 生成微信小程序 URL Scheme
     * 适用于短信、邮件、外部网页、微信内等拉起小程序的业务场景
     *
     * @param path 小程序页面路径，不填默认主页
     * @param query 页面参数，最大1024个字符
     * @param isExpire 是否到期失效，true：到期失效，false：永久有效
     * @param expireTime 到期失效时间（Unix时间戳，秒），最长有效期为1年
     * @return 生成的 scheme 码
     */
    public String generateScheme(String path, String query, Boolean isExpire, Long expireTime) {
        Map<String, Object> requestParam = new HashMap<>();

        // 跳转小程序信息
        Map<String, Object> jumpWxa = new HashMap<>();
        if (Strings.isNotBlank(path)) {
            jumpWxa.put("path", path);
        }
        if (Strings.isNotBlank(query)) {
            jumpWxa.put("query", query);
        }
        jumpWxa.put("env_version", DevOption.isDevModel() ? "develop" : "release");
        requestParam.put("jump_wxa", jumpWxa);

        // 过期设置
        if (isExpire != null && isExpire) {
            requestParam.put("is_expire", true);
            if (expireTime != null) {
                requestParam.put("expire_time", expireTime);
            }
        }

        Map<String, Object> response = WxApiRequest.generateScheme(getAccessToken(), requestParam);
        if (response.get("errcode") != null && (Integer) response.get("errcode") != SUCCESS) {
            log.error("生成微信小程序URL Scheme异常: {}", response);
            if ((Integer) response.get("errcode") == 40001) {
                deleteAccessToken();
            }
            throw Ex.business(Strings.defaultString(response.get("errmsg"), "生成URL Scheme失败"));
        }

        return Strings.defaultString(response.get("openlink"));
    }

    /**
     * 生成永久有效的微信小程序 URL Scheme
     *
     * @param path 小程序页面路径，不填默认主页
     * @param query 页面参数，最大1024个字符
     * @return 生成的 scheme 码
     */
    public String generateScheme(String path, String query) {
        return generateScheme(path, query, false, null);
    }

}
